/**
 * 
 */
package edu.byu.nlp.pipes;

import java.util.Iterator;

import com.google.common.base.Function;
import com.google.common.base.Predicate;
import com.google.common.collect.AbstractIterator;
import com.google.common.collect.Iterables;

import edu.byu.nlp.io.AbstractIterable;

/**
 * @author robbie
 *
 */
public class Pipes {

	public static <IL, ID, OL, OD> Pipe<IL, ID, OL, OD> transformingPipe(final Function<Instance<IL, ID>, Instance<OL, OD>> f) {
		return new Pipe<IL, ID, OL, OD>() {
	
			@Override
			public Iterable<Instance<OL, OD>> apply(final Iterable<Instance<IL, ID>> instances) {
				return Iterables.transform(instances, f);
			}
		};
	}

	public static <L, ID, OD> Pipe<L, ID, L, OD> dataTransformingPipe(Function<? super ID, OD> f) {
		return transformingPipe(Instances.<L, ID, OD>dataFunction(f));
	}

	public static <IL, D, OL> Pipe<IL, D, OL, D> labelTransformingPipe(Function<? super IL, OL> f) {
		return transformingPipe(Instances.<IL, D, OL>labelFunction(f));
	}

	public static <L, D> Pipe<L, D, L, D> sourceTransformingPipe(final Function<String, String> f) {
		return transformingPipe(Instances.<L, D>sourceFunction(f));
	}

	public static <L, D> Pipe<L, D, L, D> filteringPipe(final Predicate<Instance<L, D>> filter) {
		return new Pipe<L, D, L, D>() {
	
			@Override
			public Iterable<Instance<L, D>> apply(final Iterable<Instance<L, D>> instances) {
				return Iterables.filter(instances, filter);
			}
		};
	}

	public static <L, D> Pipe<L, D, L, D> dataFilteringPipe(final Predicate<D> predicate) {
		return filteringPipe(Instances.<L, D>dataPredicate(predicate));
	}

	public static <L, D> Pipe<L, D, L, D> labelFilteringPipe(final Predicate<L> predicate) {
		return filteringPipe(Instances.<L, D>labelPredicate(predicate));
	}

	public static <L, D> Pipe<L, D, L, D> sourceFilteringPipe(final Predicate<String> predicate) {
		return filteringPipe(Instances.<L, D>sourcePredicate(predicate));
	}

	private static final class OneToManyPipe<IL, ID, OL, OD> implements Pipe<IL, ID, OL, OD> {

		private final OneToManyInstanceFunction<IL, ID, OL, OD> f;

		private OneToManyPipe(OneToManyInstanceFunction<IL, ID, OL, OD> f) {
			this.f = f;
		}

		@Override
		public Iterable<Instance<OL, OD>> apply(final Iterable<Instance<IL, ID>> instance) {
			return new AbstractIterable<Instance<OL,OD>>() {

				@Override
				public Iterator<Instance<OL, OD>> iterator() {
					return new ManyIterator<IL, ID, OL, OD>(instance.iterator(), f);
				}
			};
		}
	}

	private static final class ManyIterator<IL, ID, OL, OD> extends AbstractIterator<Instance<OL, OD>> {

		private final Iterator<Instance<IL, ID>> it;

		private final OneToManyInstanceFunction<IL, ID, OL, OD> f;
		private Iterator<Instance<OL, OD>> it2;
		private Instance<IL, ID> instance;

		private ManyIterator(Iterator<Instance<IL, ID>> it, OneToManyInstanceFunction<IL, ID, OL, OD> f) {
			this.it = it;
			this.f = f;
		}

		@Override
		protected Instance<OL, OD> computeNext() {
			// A generator would be awesome here.
			while (it2 == null || !it2.hasNext()) {
				if (!it.hasNext()) {
					return endOfData();
				}
				
				instance = it.next();
				it2 = f.apply(instance);
			}
			
			return it2.next();
		}
	}

	public static <IL, ID, OL, OD> Pipe<IL, ID, OL, OD> oneToMany(OneToManyInstanceFunction<IL, ID, OL, OD> f) {
		return new OneToManyPipe<IL, ID, OL, OD>(f);
	}

	public static <L, ID, OD> Pipe<L, ID, L, OD> oneToManyData(
			final Function<ID, Iterator<OD>> f) {

		return oneToMany(Instances.<L, ID, OD>oneToManyDataFunction(f));
	}
	
	public static <L, D> Pipe<L, D, L, D> passThrough() {
		return new Pipe<L, D, L, D>() {

			@Override
			public Iterable<Instance<L, D>> apply(Iterable<Instance<L, D>> instances) {
				return instances;
			}
		};
	}

	public static <IL, ID, OL, OD, O> O apply(DataSource<IL, ID> src, Pipe<IL, ID, OL, OD> pipe,
			DataSink<OL, OD, O> sink) {
		return sink.process(pipe.apply(src.getData()));
	}

}
